#ifndef _COMPILATION_TABLE_H_
#define _COMPILATION_TABLE_H_

#include <compilation/Forward.h>

#include <map>
#include <vector>
#include <memory>
#include <iostream>

#include <compilation/instruction/Type.h>


namespace compilation
{

class SymbolTable
{
private:

  std::map<std::string, int> m_localIndexes;
  std::vector<VirtualType*> m_types;
  std::shared_ptr<SymbolTable> m_parent;

public:

  SymbolTable(void) {}

  ~SymbolTable(void)
  {
    for (std::vector<VirtualType*>::iterator iIter = m_types.begin();
         iIter != m_types.end(); ++iIter) delete *iIter;
    m_types.clear();
  }

  void addDeclaration(const std::string& name, VirtualType* type)
  {
    m_localIndexes.insert(std::pair<std::string, int>(name, m_types.size()));
    m_types.push_back(type);
  }

  bool renameDeclaration(std::string const& newName, std::string const& oldName)
  {
    auto it = m_localIndexes.find(oldName);
    if (it != m_localIndexes.end())
    {
      m_localIndexes[newName] = it->second;
      m_localIndexes.erase(it);
      return true;
    }
    return false;
  }

  bool contain(const std::string& name) const
  { return m_localIndexes.find(name) != m_localIndexes.end(); }

  int localIndex(const std::string& name) const
  { return m_localIndexes.find(name)->second; }

  const VirtualType& getType(int uIndex) const
  { return *m_types[uIndex]; }

  friend class Scope;

  void printAsDeclarations(
      std::ostream& out,
      std::function<void(void)> const& indent) const
  {
    for (std::map<std::string, int>::const_iterator iIter = m_localIndexes.begin();
         iIter != m_localIndexes.end(); ++iIter)
    {
      out << std::endl;
      if (indent) indent();
      out << "  " << m_types[iIter->second] << ' ' << iIter->first
          << " := " << iIter->second;
    }
  }

  int count(void) const
  { return m_types.size(); }
};

class Scope
{
private:

  std::shared_ptr<SymbolTable> m_last;

  Scope(const std::shared_ptr<SymbolTable>& lastSource) : m_last(lastSource) {}

public:

  inline Scope(void)
    : m_last(new SymbolTable()) {}

  inline Scope(const Scope& source)
    : m_last(source.m_last) {}

  inline void push(void)
  {
    std::shared_ptr<SymbolTable> parent = m_last;
    m_last.reset(new SymbolTable());
    m_last->m_parent = parent;
  }

  void pop(void)
  { m_last = m_last->m_parent; }

  enum FindResult
  {
    FRNotFound = 0,
    FRLocal,
    FRGlobal,
    FRType
  };

  void addDeclaration(const std::string& name, VirtualType* type)
  { m_last->addDeclaration(name, type); }

  bool renameDeclaration(std::string const& newName, std::string const& oldName)
  { return m_last->renameDeclaration(newName, oldName); }

  FindResult find(const std::string& name, int& local, Scope& scope) const;

  int getFunctionIndex(int local) const;

  void printAsDeclarations(
      std::ostream& out,
      std::function<void(void)> const& indent) const
  { m_last->printAsDeclarations(out, indent); }

  SymbolTable const& getLocalSymbolTable(void) const
  { return *m_last; }

  int count(void) const
  { return m_last->count(); }

  const VirtualType& getType(int uIndex) const
  { return m_last->getType(uIndex); }
};

} // compilation

#endif // _COMPILATION_TABLE_H_
